package org.easy.validator.core;


import org.easy.validator.constant.ValidatorEnum;
import org.easy.validator.exception.ValidatorException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;


/**
 * @author volicy.xu
 */
public class ValidatorImpl implements Validator {

    private ApplicationContext applicationContext;

    private static final String PROPERTIES_NAME = "spring.org.easyvalidator.name";

    private static final String PROPERTIES_SUFFIX = ".properties";

    private static final String MESSAGE = "message";

    private static final String REQUIRED = "required";

    private static final String ENCODE = "utf-8";


    public ValidatorImpl(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }


    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }


    @Override
    public void validate(Object target, Errors errors) {
        try {
            validate(target);

        } catch (Exception e) {

            if(e instanceof ValidatorException){
                throw new ValidatorException(e.getMessage());
            }
            throw new ValidatorException(e);
        }
    }


    /**
     * @param target
     */
    public void validate(Object target) throws IllegalAccessException, IOException {
        Class<?> clazz = target.getClass();
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            if (isPrivate(field)) {
                field.setAccessible(true);
                resolveAnnotations(field.getDeclaredAnnotations(), field.get(target));
            }
        }
    }


    /**
     * @param annotations
     * @param value
     */
    public void resolveAnnotations(Annotation[] annotations, Object value) throws IOException {
        for (int i = 0; annotations != null && annotations.length > 0 && i < annotations.length; i++) {
            Annotation annotation = annotations[i];
            if (isEasyValid(annotation.annotationType())) {
                valid(annotation, value, this);
            }
        }
    }

    /**
     * @param annotation
     * @param validator
     * @throws IOException
     */
    public static void valid(Annotation annotation, Object value, ValidatorImpl validator) throws IOException {
        boolean required = (boolean) AnnotationUtils.getValue(annotation, REQUIRED);
        if (required) {
            ValidatorEnum validConstant = ValidatorEnum.getInstance(annotation.annotationType().getSimpleName());
            if (validConstant != null) {
                String message = validator.getValue(annotation);
                if (!StringUtils.hasText(message)) {
                    throw new ValidatorException("Correctly configure annotation message property");
                }
                validConstant.valiated(value, message);
            }else {
                throw new ValidatorException("Correctly configure easyvalidator annotation");
            }
        }
    }

    /**
     * @return
     * @throws IOException
     */
    public Environment getEnvironment() throws IOException {
        String value = applicationContext.getEnvironment().getProperty(PROPERTIES_NAME);
        if (value != null) {
            AbstractEnvironment environment = (AbstractEnvironment) applicationContext.getEnvironment();
            environment.getPropertySources().addFirst(new ResourcePropertySource(value,
                    new EncodedResource(new ClassPathResource(value + PROPERTIES_SUFFIX), ENCODE)));
            return environment;
        }
        return null;
    }


    /**
     * @param annotation
     * @return
     * @throws IOException
     */
    public String getValue(Annotation annotation) throws IOException {
        String message = (String) AnnotationUtils.getValue(annotation, MESSAGE);
        if (message.indexOf('{') > -1) {
            Environment environment = getEnvironment();
            if (environment == null) {
                return null;
            }
            return environment.resolveRequiredPlaceholders(message);
        }
        return message;
    }


    /**
     * @param annType
     * @return
     */
    public static boolean isEasyValid(Class annType) {
        return annType.getName().indexOf("org.easy.validator") > -1;
    }


    /**
     * @param field
     * @return
     */
    public static boolean isPrivate(Field field) {
        return !Modifier.isStatic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers());
    }
}
